﻿
using System;
using System.ComponentModel;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;

namespace MVCSpike
{
    public abstract class MvcControlBase<TModel, TView> : IDisposable where TModel : MvcModelBase where TView : class, IMvcView 
    {
        #region Member Variables

        protected TView m_view;
        protected TModel m_model;

        #endregion

        #region Abstract Methods

        //protected abstract void WireEvents();
        protected abstract void UnwireEvents();

        #endregion

        #region Methods

        public void Initialize(TModel model, TView view)
        {
            if (m_model != null || m_view != null)
                UnwireEvents();

            m_model = model;
            m_view = view;

            SetViewState();
            WireEvents();
        }

        private readonly Dictionary<String, ModelViewPropertyMap> m_viewModelMap = 
            new Dictionary<string, ModelViewPropertyMap>();

        private void SetViewState()
        {
            CreatePropertyMap();
            foreach (var propertyName in m_viewModelMap.Keys)
            {
                SetViewProperty(propertyName);
            }
            DoAfterSetViewState();
        }

        protected  virtual void DoAfterSetViewState()
        {
            return;
        }

        private void SetViewProperty(string propertyName)
        {
            var map = m_viewModelMap[propertyName];
            map.ViewProperty.SetValue(m_view, map.ModelProperty.GetValue(m_model, null), null);
        }

        private void WireEvents()
        {        
            Model.PropertyChanged += Model_PropertyChanged;
            DoAfterWireEvents();
        }

        protected virtual void DoAfterWireEvents()
        {
            return;
        }
        
        private void CreatePropertyMap()
        {
            var view = m_view.GetType();
            var props =
                (view.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)).
                    Where(p => p.GetCustomAttributes(typeof(MvcPropertyAttribute), true).Count() > 0);

            m_viewModelMap.Clear();
            foreach (var prop in props)
            {
                AddMap(prop);
            }
        }

        
        // sets the view property if the model has changed
        protected void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!m_viewModelMap.Keys.Contains(e.PropertyName)) return;
            SetViewProperty(e.PropertyName);
        }

        private void AddMap(PropertyInfo prop)
        {
            var modelPropertyName =
                ((MvcPropertyAttribute) ((prop.GetCustomAttributes(typeof(MvcPropertyAttribute), true)).Single()))
                    .ModelProperyName;
                
            var modelProperty = ModelViewPropertyMap.GetModelProperty(m_model.GetType(), modelPropertyName);
            if ( modelProperty != null)
            {
                var map = new ModelViewPropertyMap(modelProperty, prop);
                m_viewModelMap.Add(modelPropertyName, map);
            }
            else
            {
                Console.WriteLine(String.Format("Model Property[{0}] was not found so it was not added to the map collection",
                    modelPropertyName));
            }
        }

        #endregion

        #region Properties

        public TModel Model { get { return m_model; } }
        public TView View { get { return m_view; } }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            UnwireEvents();
        }

        #endregion

        private class ModelViewPropertyMap
        {
            public ModelViewPropertyMap(PropertyInfo modelProperty, PropertyInfo viewProperty)
            {
                ModelProperty = modelProperty;
                ViewProperty = viewProperty;
            }

            public PropertyInfo ModelProperty { get; private set; }
            public PropertyInfo ViewProperty { get; private set; }

            public static PropertyInfo GetModelProperty(Type type, string name)
            {
                return type.GetProperty(name);
            }
        }
    }
}